﻿using System;
using System.Collections.Generic;

using System.Text;

namespace System.Text
{
    /*******************************/
    /// <summary>
    /// Implements number format functions
    /// </summary>
    [Serializable]
    public class TextNumberFormat
    {

        //Current localization number format infomation
        private System.Globalization.NumberFormatInfo numberFormat;
        //Enumeration of format types that can be used
        private enum formatTypes { General, Number, Currency, Percent };
        //Current format type used in the instance
        private int numberFormatType;
        //Indicates if grouping is being used
        private bool groupingActivated;
        //Current separator used
        private string separator;
        //Number of maximun digits in the integer portion of the number to represent the number
        private int maxIntDigits;
        //Number of minimum digits in the integer portion of the number to represent the number
        private int minIntDigits;
        //Number of maximun digits in the fraction portion of the number to represent the number
        private int maxFractionDigits;
        //Number of minimum digits in the integer portion of the number to represent the number
        private int minFractionDigits;

        /// <summary>
        /// Initializes a new instance of the object class with the default values
        /// </summary>
        public TextNumberFormat()
        {
            this.numberFormat = new System.Globalization.NumberFormatInfo();
            this.numberFormatType = (int)TextNumberFormat.formatTypes.General;
            this.groupingActivated = true;
            this.separator = this.GetSeparator((int)TextNumberFormat.formatTypes.General);
            this.maxIntDigits = 127;
            this.minIntDigits = 1;
            this.maxFractionDigits = 3;
            this.minFractionDigits = 0;
        }

        /// <summary>
        /// Sets the Maximum integer digits value. 
        /// </summary>
        /// <param name="newValue">the new value for the maxIntDigits field</param>
        public void setMaximumIntegerDigits(int newValue)
        {
            maxIntDigits = newValue;
            if (newValue <= 0)
            {
                maxIntDigits = 0;
                minIntDigits = 0;
            }
            else if (maxIntDigits < minIntDigits)
            {
                minIntDigits = maxIntDigits;
            }
        }

        public ValueType parse(string value, int position) {
            return double.Parse(value);
            //TODO
        }

        /// <summary>
        /// Sets the minimum integer digits value. 
        /// </summary>
        /// <param name="newValue">the new value for the minIntDigits field</param>
        public void setMinimumIntegerDigits(int newValue)
        {
            minIntDigits = newValue;
            if (newValue <= 0)
            {
                minIntDigits = 0;
            }
            else if (maxIntDigits < minIntDigits)
            {
                maxIntDigits = minIntDigits;
            }
        }

        public void setParseIntegerOnly(bool only) { 
            //TODO
        }

        /// <summary>
        /// Sets the maximum fraction digits value. 
        /// </summary>
        /// <param name="newValue">the new value for the maxFractionDigits field</param>
        public void setMaximumFractionDigits(int newValue)
        {
            maxFractionDigits = newValue;
            if (newValue <= 0)
            {
                maxFractionDigits = 0;
                minFractionDigits = 0;
            }
            else if (maxFractionDigits < minFractionDigits)
            {
                minFractionDigits = maxFractionDigits;
            }
        }

        /// <summary>
        /// Sets the minimum fraction digits value. 
        /// </summary>
        /// <param name="newValue">the new value for the minFractionDigits field</param>
        public void setMinimumFractionDigits(int newValue)
        {
            minFractionDigits = newValue;
            if (newValue <= 0)
            {
                minFractionDigits = 0;
            }
            else if (maxFractionDigits < minFractionDigits)
            {
                maxFractionDigits = minFractionDigits;
            }
        }

        /// <summary>
        /// Initializes a new instance of the class with the specified number format
        /// and the amount of fractional digits to use
        /// </summary>
        /// <param name="theType">Number format</param>
        /// <param name="digits">Number of fractional digits to use</param>
        private TextNumberFormat(TextNumberFormat.formatTypes theType, int digits)
        {
            this.numberFormat = System.Globalization.NumberFormatInfo.CurrentInfo;
            this.numberFormatType = (int)theType;
            this.groupingActivated = true;
            this.separator = this.GetSeparator((int)theType);
            this.maxIntDigits = 127;
            this.minIntDigits = 1;
            this.maxFractionDigits = 3;
            this.minFractionDigits = 0;
        }

        /// <summary>
        /// Initializes a new instance of the class with the specified number format,
        /// uses the system's culture information,
        /// and assigns the amount of fractional digits to use
        /// </summary>
        /// <param name="theType">Number format</param>
        /// <param name="cultureNumberFormat">Represents information about a specific culture including the number formatting</param>
        /// <param name="digits">Number of fractional digits to use</param>
        private TextNumberFormat(TextNumberFormat.formatTypes theType, System.Globalization.CultureInfo cultureNumberFormat, int digits)
        {
            this.numberFormat = cultureNumberFormat.NumberFormat;
            this.numberFormatType = (int)theType;
            this.groupingActivated = true;
            this.separator = this.GetSeparator((int)theType);
            this.maxIntDigits = 127;
            this.minIntDigits = 1;
            this.maxFractionDigits = 3;
            this.minFractionDigits = 0;
        }

        /// <summary>
        /// Returns an initialized instance of the TextNumberFormat object
        /// using number representation.
        /// </summary>
        /// <returns>The object instance</returns>
        public static TextNumberFormat getTextNumberInstance()
        {
            TextNumberFormat instance = new TextNumberFormat(TextNumberFormat.formatTypes.Number, 3);
            return instance;
        }

        /// <summary>
        /// Returns an initialized instance of the TextNumberFormat object
        /// using currency representation.
        /// </summary>
        /// <returns>The object instance</returns>
        public static TextNumberFormat getTextNumberCurrencyInstance()
        {
            TextNumberFormat instance = new TextNumberFormat(TextNumberFormat.formatTypes.Currency, 3);
            return instance.setToCurrencyNumberFormatDefaults(instance);
        }

        /// <summary>
        /// Returns an initialized instance of the TextNumberFormat object
        /// using percent representation.
        /// </summary>
        /// <returns>The object instance</returns>
        public static TextNumberFormat getTextNumberPercentInstance()
        {
            TextNumberFormat instance = new TextNumberFormat(TextNumberFormat.formatTypes.Percent, 3);
            return instance.setToPercentNumberFormatDefaults(instance);
        }

        /// <summary>
        /// Returns an initialized instance of the TextNumberFormat object
        /// using number representation, it uses the culture format information provided.
        /// </summary>
        /// <param name="culture">Represents information about a specific culture</param>
        /// <returns>The object instance</returns>
        public static TextNumberFormat getTextNumberInstance(System.Globalization.CultureInfo culture)
        {
            TextNumberFormat instance = new TextNumberFormat(TextNumberFormat.formatTypes.Number, culture, 3);
            return instance;
        }

        /// <summary>
        /// Returns an initialized instance of the TextNumberFormat object
        /// using currency representation, it uses the culture format information provided.
        /// </summary>
        /// <param name="culture">Represents information about a specific culture</param>
        /// <returns>The object instance</returns>
        public static TextNumberFormat getTextNumberCurrencyInstance(System.Globalization.CultureInfo culture)
        {
            TextNumberFormat instance = new TextNumberFormat(TextNumberFormat.formatTypes.Currency, culture, 3);
            return instance.setToCurrencyNumberFormatDefaults(instance);
        }

        /// <summary>
        /// Returns an initialized instance of the TextNumberFormat object
        /// using percent representation, it uses the culture format information provided.
        /// </summary>
        /// <param name="culture">Represents information about a specific culture</param>
        /// <returns>The object instance</returns>
        public static TextNumberFormat getTextNumberPercentInstance(System.Globalization.CultureInfo culture)
        {
            TextNumberFormat instance = new TextNumberFormat(TextNumberFormat.formatTypes.Percent, culture, 3);
            return instance.setToPercentNumberFormatDefaults(instance);
        }

        /// <summary>
        /// Clones the object instance
        /// </summary>
        /// <returns>The cloned object instance</returns>
        public System.Object Clone()
        {
            return (System.Object)this;
        }

        /// <summary>
        /// Determines if the received object is equal to the
        /// current object instance
        /// </summary>
        /// <param name="textNumberObject">TextNumber instance to compare</param>
        /// <returns>True or false depending if the two instances are equal</returns>
        public override bool Equals(Object obj)
        {
            // Check for null values and compare run-time types.
            if (obj == null || GetType() != obj.GetType())
                return false;
            TextNumberFormat param = (TextNumberFormat)obj;
            return (numberFormat == param.numberFormat) && (numberFormatType == param.numberFormatType)
                && (groupingActivated == param.groupingActivated) && (separator == param.separator)
                && (maxIntDigits == param.maxIntDigits) && (minIntDigits == param.minIntDigits)
                && (maxFractionDigits == param.maxFractionDigits) && (minFractionDigits == param.minFractionDigits);
        }


        /// <summary>
        /// Serves as a hash function for a particular type, suitable for use in hashing algorithms and data structures like a hash table.
        /// </summary>
        /// <returns>A hash code for the current Object</returns>
        public override int GetHashCode()
        {
            return numberFormat.GetHashCode() ^ numberFormatType ^ groupingActivated.GetHashCode()
                 ^ separator.GetHashCode() ^ maxIntDigits ^ minIntDigits ^ maxFractionDigits ^ minFractionDigits;
        }

        /// <summary>
        /// Formats a number with the current formatting parameters
        /// </summary>
        /// <param name="number">Source number to format</param>
        /// <returns>The formatted number string</returns>
        public string FormatDouble(double number)
        {
            if (this.groupingActivated)
            {
                return SetIntDigits(number.ToString(this.GetCurrentFormatString() + this.GetNumberOfDigits(number), this.numberFormat));
            }
            else
            {
                return SetIntDigits((number.ToString(this.GetCurrentFormatString() + this.GetNumberOfDigits(number), this.numberFormat)).Replace(this.separator, ""));
            }
        }

        /// <summary>
        /// Formats a number with the current formatting parameters
        /// </summary>
        /// <param name="number">Source number to format</param>
        /// <returns>The formatted number string</returns>
        public string FormatLong(long number)
        {
            if (this.groupingActivated)
            {
                return SetIntDigits(number.ToString(this.GetCurrentFormatString() + this.minFractionDigits, this.numberFormat));
            }
            else
            {
                return SetIntDigits((number.ToString(this.GetCurrentFormatString() + this.minFractionDigits, this.numberFormat)).Replace(this.separator, ""));
            }
        }


        /// <summary>
        /// Formats the number according to the specified number of integer digits 
        /// </summary>
        /// <param name="number">The number to format</param>
        /// <returns></returns>
        private string SetIntDigits(String number)
        {
            String decimals = "";
            String fraction = "";
            int i = number.IndexOf(this.numberFormat.NumberDecimalSeparator);
            if (i > 0)
            {
                fraction = number.Substring(i);
                decimals = number.Substring(0, i).Replace(this.numberFormat.NumberGroupSeparator, "");
            }
            else decimals = number.Replace(this.numberFormat.NumberGroupSeparator, "");
            decimals = decimals.PadLeft(this.MinIntDigits, '0');
            if ((i = decimals.Length - this.MaxIntDigits) > 0) decimals = decimals.Remove(0, i);
            if (this.groupingActivated)
            {
                for (i = decimals.Length; i > 3; i -= 3)
                {
                    decimals = decimals.Insert(i - 3, this.numberFormat.NumberGroupSeparator);
                }
            }
            decimals = decimals + fraction;
            if (decimals.Length == 0) return "0";
            else return decimals;
        }

        /// <summary>
        /// Gets the list of all supported cultures
        /// </summary>
        /// <returns>An array of type CultureInfo that represents the supported cultures</returns>
        public static System.Globalization.CultureInfo[] GetAvailableCultures()
        {
            return System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.AllCultures);
        }

        /// <summary>
        /// Obtains the current format representation used
        /// </summary>
        /// <returns>A character representing the string format used</returns>
        private string GetCurrentFormatString()
        {
            string currentFormatString = "n";  //Default value
            switch (this.numberFormatType)
            {
                case (int)TextNumberFormat.formatTypes.Currency:
                    currentFormatString = "c";
                    break;

                case (int)TextNumberFormat.formatTypes.General:
                    currentFormatString = "n";
                    break;

                case (int)TextNumberFormat.formatTypes.Number:
                    currentFormatString = "n";
                    break;

                case (int)TextNumberFormat.formatTypes.Percent:
                    currentFormatString = "p";
                    break;
            }
            return currentFormatString;
        }

        /// <summary>
        /// Retrieves the separator used, depending on the format type specified
        /// </summary>
        /// <param name="numberFormatType">formatType enumarator value to inquire</param>
        /// <returns>The values of character separator used </returns>
        private string GetSeparator(int numberFormatType)
        {
            string separatorItem = " ";  //Default Separator

            switch (numberFormatType)
            {
                case (int)TextNumberFormat.formatTypes.Currency:
                    separatorItem = this.numberFormat.CurrencyGroupSeparator;
                    break;

                case (int)TextNumberFormat.formatTypes.General:
                    separatorItem = this.numberFormat.NumberGroupSeparator;
                    break;

                case (int)TextNumberFormat.formatTypes.Number:
                    separatorItem = this.numberFormat.NumberGroupSeparator;
                    break;

                case (int)TextNumberFormat.formatTypes.Percent:
                    separatorItem = this.numberFormat.PercentGroupSeparator;
                    break;
            }
            return separatorItem;
        }

        /// <summary>
        /// Boolean value stating if grouping is used or not
        /// </summary>
        public bool GroupingUsed
        {
            get
            {
                return (this.groupingActivated);
            }
            set
            {
                this.groupingActivated = value;
            }
        }

        /// <summary>
        /// Minimum number of integer digits to use in the number format
        /// </summary>
        public int MinIntDigits
        {
            get
            {
                return this.minIntDigits;
            }
            set
            {
                this.minIntDigits = value;
            }
        }

        /// <summary>
        /// Maximum number of integer digits to use in the number format
        /// </summary>
        public int MaxIntDigits
        {
            get
            {
                return this.maxIntDigits;
            }
            set
            {
                this.maxIntDigits = value;
            }
        }

        /// <summary>
        /// Minimum number of fraction digits to use in the number format
        /// </summary>
        public int MinFractionDigits
        {
            get
            {
                return this.minFractionDigits;
            }
            set
            {
                this.minFractionDigits = value;
            }
        }

        /// <summary>
        /// Maximum number of fraction digits to use in the number format
        /// </summary>
        public int MaxFractionDigits
        {
            get
            {
                return this.maxFractionDigits;
            }
            set
            {
                this.maxFractionDigits = value;
            }
        }

        /// <summary>
        /// Sets the values of minFractionDigits and maxFractionDigits to the currency standard
        /// </summary>
        /// <param name="format">The TextNumberFormat instance to set</param>
        /// <returns>The TextNumberFormat with corresponding the default values</returns>
        private TextNumberFormat setToCurrencyNumberFormatDefaults(TextNumberFormat format)
        {
            format.maxFractionDigits = 2;
            format.minFractionDigits = 2;
            return format;
        }

        /// <summary>
        /// Sets the values of minFractionDigits and maxFractionDigits to the percent standard
        /// </summary>
        /// <param name="format">The TextNumberFormat instance to set</param>
        /// <returns>The TextNumberFormat with corresponding the default values</returns>
        private TextNumberFormat setToPercentNumberFormatDefaults(TextNumberFormat format)
        {
            format.maxFractionDigits = 0;
            format.minFractionDigits = 0;
            return format;
        }

        /// <summary>
        /// Gets the number of fraction digits thats must be used by the format methods
        /// </summary>
        /// <param name="number">The double number</param>
        /// <returns>The number of fraction digits to use</returns>
        private int GetNumberOfDigits(Double number)
        {
            int counter = 0;
            double temp = System.Math.Abs(number);
            while ((temp % 1) > 0)
            {
                temp *= 10;
                counter++;
            }
            return (counter < this.minFractionDigits) ? this.minFractionDigits : ((counter < this.maxFractionDigits) ? counter : this.maxFractionDigits);
        }
    }
}
